/*
 * Copyright (c) 2006 KOZUKA Masahiro  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <glib.h>
#include <assert.h>

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "adaptation.h"
#include "distribution.h"
#include "auth.h"
#include "bundling.h"
#include "pathmanagement.h"
#include "auxiliary.h"
#include "md5.h"
#include "sha1.h"

typedef union AUTH_DIGEST_CTX {
    SHA1Context sha1;
    MD5_CTX md5;
} auth_digest_ctx;


typedef struct AUTH_DIGEST {
    int id;
    int length;
    int block_length;
    void (*initialize)(auth_digest_ctx *);
    void (*update)(auth_digest_ctx *, unsigned char *, size_t);
    void (*digest)(auth_digest_ctx *, unsigned char *, size_t *);
} auth_digest;

static void auth_digest_sha1_initialize(auth_digest_ctx *);
static void auth_digest_sha1_update(auth_digest_ctx *, unsigned char *, size_t);
static void auth_digest_sha1_digest(auth_digest_ctx *, unsigned char *, size_t *);
static void auth_digest_md5_initialize(auth_digest_ctx *);
static void auth_digest_md5_update(auth_digest_ctx *, unsigned char *, size_t);
static void auth_digest_md5_digest(auth_digest_ctx *, unsigned char *, size_t *);
    
static void auth_digest_md5_initialize(auth_digest_ctx *);
auth_digest auth_digests[] = {
    {
	SCTP_HMAC_RESERVED, /* Reserved */
	-1,
	-1,
	NULL,
	NULL,
	NULL
    },
    {
	SCTP_HMAC_SHA1,
	SCTP_AUTH_DIGEST_LEN_SHA1,
	SCTP_AUTH_DIGEST_BLOCK_LEN_SHA1,
	auth_digest_sha1_initialize,
	auth_digest_sha1_update,
	auth_digest_sha1_digest
    },
    {
	SCTP_HMAC_MD5,
	SCTP_AUTH_DIGEST_LEN_MD5,
	SCTP_AUTH_DIGEST_BLOCK_LEN_MD5,
	auth_digest_md5_initialize,
	auth_digest_md5_update,
	auth_digest_md5_digest
    },
};

static unsigned int auth_digestx(unsigned int hmac_id)
{
    switch (hmac_id) {
	case SCTP_HMAC_SHA1:
	    return SCTP_HMAC_SHA1;
	case SCTP_HMAC_MD5:
	    return SCTP_HMAC_MD5;
	default:
	    return 0;
    }
}

static void auth_digest_sha1_initialize(auth_digest_ctx *ctx)
{
    SHA1Reset((SHA1Context *)ctx);
}

static void auth_digest_sha1_update(auth_digest_ctx *ctx,
    unsigned char *input, size_t input_len)
{
    SHA1Input((SHA1Context *)ctx, input, input_len);
}

static void auth_digest_sha1_digest(auth_digest_ctx *ctx,
    unsigned char *output, size_t *output_len)
{
    if (*output_len < SCTP_AUTH_DIGEST_LEN_SHA1) {
	return;
    }
    SHA1Result((SHA1Context *)ctx, output);
    *output_len = SCTP_AUTH_DIGEST_LEN_SHA1;
}

static void auth_digest_md5_initialize(auth_digest_ctx *ctx)
{
    MD5Init((MD5_CTX *)ctx);
}

static void auth_digest_md5_update(auth_digest_ctx *ctx,
    unsigned char *input, size_t input_len)
{
    MD5Update((MD5_CTX *)ctx, input, input_len);
}

static void auth_digest_md5_digest(auth_digest_ctx *ctx,
    unsigned char *output, size_t *output_len)
{
    if (*output_len < SCTP_AUTH_DIGEST_LEN_MD5) {
	return;
    }
    MD5Final(output, (MD5_CTX *)ctx);
    *output_len = SCTP_AUTH_DIGEST_LEN_MD5;
}

#define MAX_AUTH_KEY 256

typedef struct AUTH_KEY {
    unsigned char data[MAX_AUTH_KEY];
    size_t	length;
}auth_key;


typedef struct AUTH_SHARED_KEY {
    auth_key	key;
    guint16	key_id;
}auth_shared_key;


typedef struct AUTH_DATA {
    GArray		*shared_keys;
    unsigned int	noOf_shared_keys;
    auth_key		random;
    auth_key		peer_random;
    unsigned char	chklist[MAX_AUTH_CHUNK_LIST];
    unsigned char	peer_chklist[MAX_AUTH_CHUNK_LIST];
    unsigned short 	key_id;
    auth_key		*key;
    unsigned short	hmac_id;
    auth_digest		*digest;
}auth_data;


int auth_add_random_parameter(ChunkID initCID)
{
    unsigned char random[MAX_AUTH_RANDOM];
    size_t random_length = 0, i;
    unsigned char *ptr;
    unsigned int random_number = 0;

    random_length = 32;
    for (ptr = random;
	 ptr < random + random_length;
	 ptr += sizeof(unsigned int)) {
	*((unsigned int *)ptr) = adl_random();
    }
    if (random_length % 4 != 0) {
	random_number = adl_random();
	ptr -= (random_length % 4);
	for (i = 0; i < random_length % 4; i++) {
	    ptr[i] = (random_number >> (i * 8)) || 0xff;
	}
    }

    ch_addParameterToInitChunk(initCID, VLPARAM_RANDOM, random_length, random);

    return 0;
}


int auth_add_chklist_parameter(ChunkID initCID,
    unsigned char *chklist, size_t chklist_size)
{
    unsigned char temp[MAX_AUTH_CHUNK_LIST];
    size_t i, ii;

    event_logi(VVERBOSE, "auth_add_chklist_parameter: chklist_size(%d)",
	chklist_size);

    ii = 0;
    for (i = 0; i < chklist_size && ii < sizeof(temp); i++) {
	temp[ii++] = chklist[i];
    }
    if (ii > 0) {
	ch_addParameterToInitChunk(initCID, VLPARAM_CHUNKS, ii, temp);
    }

    return 0;
}


int auth_add_hmaclist_parameter(ChunkID initCID,
    unsigned short *hmaclist, size_t hmaclist_size)
{
    unsigned short *temp;
    size_t i;

    event_logi(VVERBOSE, "auth_add_hmaclist_parameter: hmaclist_size(%d)", hmaclist_size);
    temp = (unsigned short *)malloc(sizeof(unsigned short) * hmaclist_size);
    if (temp == NULL) {
	return -1;
    }

    for (i = 0; i < hmaclist_size / sizeof(unsigned short); i++) {
	temp[i] = htons(hmaclist[i]);
    }
    ch_addParameterToInitChunk(initCID, VLPARAM_HMAC_ALGO,
	sizeof(unsigned short ) * i, (unsigned char *)temp);

    return 0;
}


int auth_init(void)
{
    return 0;
}


void *auth_new(void)
{
    auth_data *ptr;

    event_log(VVERBOSE, "auth_new");
    ptr = malloc(sizeof(*ptr));
    if(!ptr) {
	error_log(ERROR_FATAL, "Malloc failed in auth_new()");
	return NULL;
    }

    memset(ptr, 0, sizeof(*ptr));

    ptr->shared_keys =
	g_array_new(FALSE, FALSE, sizeof(auth_shared_key));
    if(!ptr->shared_keys) {
	error_log(ERROR_FATAL, "g_array_new failed in auth_new()");
	free(ptr);
	return NULL;
    }

    return ptr;
}


int auth_delete(void *ptr)
{
    auth_data *authd = (auth_data *)ptr;
    int i;

    event_logi(VVERBOSE, "auth_delete: ptr(%p)", ptr);
    if (authd->noOf_shared_keys > 0) {
	for (i = 0; i < authd->noOf_shared_keys; i++) {
	    g_array_remove_index(authd->shared_keys, 0);
	}
	g_array_free(authd->shared_keys, FALSE);
    }

    free(authd);

    return 0;
}


int auth_recv_random_parameter(ChunkID localInitCID, ChunkID peerInitCID)
{
    auth_data *authd = NULL;

    event_log(VVERBOSE, "auth_recv_random_parameter");
    authd = (auth_data *)mdi_readSCTP_auth();
    if (!authd) {
	error_log(ERROR_MAJOR, "AUTH instance not set in auth_set_random()");
	return -1;
    }

    authd->random.length = sizeof(authd->random.data);
    if (ch_getRandomFromInit(localInitCID, authd->random.data,
	&authd->random.length) < 0) {
	return -1;
    }

    authd->peer_random.length = sizeof(authd->peer_random.data);
    if (ch_getRandomFromInit(peerInitCID, authd->peer_random.data,
	&authd->peer_random.length) < 0) {
	return -1;
    }

    return 0;
}


int auth_recv_chklist_parameter(ChunkID localInitCID, ChunkID peerInitCID)
{
    auth_data *authd = NULL;
    unsigned char temp[MAX_AUTH_CHUNK_LIST];
    size_t temp_length = 0;
    size_t i;

    event_log(VVERBOSE, "auth_recv_chklist_parameter");
    authd = (auth_data *)mdi_readSCTP_auth();
    if (!authd) {
	error_log(ERROR_MAJOR, "AUTH instance not set in auth_recv_chklist()");
	return -1;
    }

    temp_length = sizeof(temp);
    if (ch_getChklistFromInit(localInitCID, temp, &temp_length) == 0) {
	for (i = 0; i < temp_length; i++) {
	    authd->chklist[temp[i]] = 1;
	}
    }

    temp_length = sizeof(temp);
    if (ch_getChklistFromInit(peerInitCID, temp, &temp_length) == 0) {
	for (i = 0; i < temp_length; i++) {
	    authd->peer_chklist[temp[i]] = 1;
	}
    }

    return 0;
}

int auth_compare_key(auth_key *key1, auth_key *key2) {
    unsigned char temp[MAX_AUTH_RANDOM];
    size_t max, i;
    unsigned char *key1_data;
    unsigned char *key2_data;

    event_logii(VVERBOSE, "auth_compare_key: key1 length(%d), key2 length(%d)",
	key1->length, key2->length);
    if (key1->length != key2->length) {
	memset(temp, 0, sizeof(temp));
	if (key1->length > key2->length) {
	    max = key1->length;
	} else {
	    max = key2->length;
	}
	if (key1->length < max) {
	    memcpy(temp + (max - key1->length), key1->data, key1->length);
	    key1_data = temp;
	    key2_data = key2->data;
	} else { 
	    memcpy(temp + (max - key2->length), key2->data, key2->length);
	    key1_data = key1->data;
	    key2_data = temp;
	}
    } else {
	max = key1->length;
	key1_data = key1->data;
	key2_data = key2->data;
    }

    for (i = 0; i < max; i++) {
	if (key1_data[i] > key2_data[i]) {
	    return 1;
	} else if (key1_data[i] < key2_data[i]) {
	    return -1;
	}
    }

    if (key1->length == key2->length) {
	return 0;
    } else if (key1->length > key2->length) {
	return 1;
    } else {
	return -1;
    }
}


auth_key *auth_compute_key(auth_key *random, auth_key *peer_random,
    auth_key *shared_key)
{
    auth_key *new_key = NULL;
    unsigned char *new_key_ptr = NULL;

    event_log(VVERBOSE, "auth_compute_key");
    if (random == NULL || peer_random == NULL) {
	return NULL;
    }

    new_key = (auth_key *)malloc(sizeof(auth_key));
    if (new_key == NULL) {
	return NULL;
    }
    memset(new_key, 0, sizeof(*new_key));
    new_key_ptr = new_key->data;

    if (auth_compare_key(random, peer_random) < 0) {
	memcpy(new_key_ptr, random->data, random->length);
	new_key_ptr += random->length;
	if (shared_key != NULL) {
	    memcpy(new_key_ptr, shared_key->data, shared_key->length);
	    new_key_ptr += shared_key->length;
	}
	memcpy(new_key_ptr, peer_random->data, peer_random->length);
	new_key_ptr += peer_random->length;
    } else {
	memcpy(new_key_ptr, peer_random->data, peer_random->length);
	new_key_ptr += peer_random->length;
	if (shared_key != NULL) {
	    memcpy(new_key_ptr, shared_key->data, shared_key->length);
	    new_key_ptr += shared_key->length;
	}
	memcpy(new_key_ptr, random->data, random->length);
	new_key_ptr += random->length;
    }

    new_key->length = new_key_ptr - new_key->data;

    return new_key;
}


int auth_recv_hmacalgo_parameter(ChunkID localInitCID, ChunkID peerInitCID)
{
    auth_data *authd = NULL;
    unsigned short *hmac_algo;
    unsigned short hmac_algo_length = 0;
    unsigned short *peer_hmac_algo;
    unsigned short peer_hmac_algo_length = 0;
    size_t i, ii;

    event_log(VVERBOSE, "auth_recv_hmacalgo_parameter");
    authd = (auth_data *)mdi_readSCTP_auth();
    if (!authd) {
	error_log(ERROR_MAJOR, "AUTH instance not set in auth_recv_hmacalgo_parameter()");
	return -1;
    }

    if (ch_getHMACAlgoFromInit(localInitCID, &hmac_algo, &hmac_algo_length) < 0) {
	error_log(ERROR_MAJOR, "no HMAC-ALGO in localInitCID");
	return -1;
    }

    if (ch_getHMACAlgoFromInit(peerInitCID, &peer_hmac_algo, &peer_hmac_algo_length) < 0) {
	error_log(ERROR_MAJOR, "no HMAC-ALGO in peerInitCID");
	return -1;
    }

    for (i = 0; i < peer_hmac_algo_length / 2; i++) {
	for (ii = 0; i < hmac_algo_length / 2; ii++) {
	    if (peer_hmac_algo[i] == hmac_algo[ii]) {
		authd->hmac_id = ntohs(peer_hmac_algo[i]);
		authd->digest = &auth_digests[auth_digestx(authd->hmac_id)];
		if (authd->digest->id == 0) {
		    error_logi(ERROR_MAJOR, "not supported HMAC_ID: %d", authd->hmac_id);
		    return -1;
		}
		return 0;
	    }
	}
    }

    return -1;
}


int auth_is_required_sending(unsigned char chunk_type)
{
    auth_data *authd = NULL;
    authd = (auth_data *)mdi_readSCTP_auth();
    if (!authd) {
	return 0;
    }

    return authd->peer_chklist[chunk_type];
}


int auth_is_required_receiving(unsigned char chunk_type)
{
    auth_data *authd = NULL;
    authd = (auth_data *)mdi_readSCTP_auth();
    if (!authd) {
	return 0;
    }

    if (chunk_type == CHUNK_ASCONF || chunk_type == CHUNK_ASCONF_ACK) {
	return 1;
    }
    return authd->chklist[chunk_type];
}


static int
auth_compute_hmac(auth_digest *, auth_key *, unsigned char *, size_t,
    unsigned char *, size_t *);
static int
auth_compute_hmac_buf(auth_digest *, auth_key *, adl_message_buf *,
    unsigned char *, size_t *);


SCTP_auth_chunk *
auth_add_auth_chunk(unsigned char *chunk_data, size_t chunk_data_size)
{
    auth_data *authd = NULL;
    SCTP_auth_chunk *auth_chunk;
    unsigned short length = 0;

    event_logi(VVERBOSE, "auth_add_auth_chunk: chunk_data_size(%d)",
	chunk_data_size);
    authd = (auth_data *)mdi_readSCTP_auth();
    if (!authd) {
	error_log(ERROR_MAJOR, "AUTH instance not set in auth_add_auth_chunk()");
	return NULL;
    }

    length =  FIXED_AUTH_CHUNK_SIZE + authd->digest->length;
    if (chunk_data_size < length) {
	error_logii(ERROR_MAJOR, "chunk data too short: %d(needed), %d(actual)",
	    length, chunk_data_size);
	return NULL;
    }
    event_logi(VVERBOSE, "auth_add_auth_chunk: length(%d)", length);

    auth_chunk = (SCTP_auth_chunk *)chunk_data;
    memset(auth_chunk, 0, length);

    auth_chunk->chunk_header.chunk_id = CHUNK_AUTH;
    auth_chunk->chunk_header.chunk_length = htons(length);
    auth_chunk->shared_key_id = 0;
    auth_chunk->hmac_id = htons(authd->hmac_id);

    return auth_chunk;
}


int auth_fill_auth_chunk(SCTP_auth_chunk *auth_chunk, adl_message_buf *buf)
{
    auth_data *authd = NULL;
    unsigned int length = 0;
    size_t computed_hmac_length = 0;
    unsigned char computed_hmac[SCTP_AUTH_DIGEST_LEN_MAX];

    event_log(VVERBOSE, "auth_fill_auth_chunk");
    authd = (auth_data *)mdi_readSCTP_auth();
    if (!authd) {
	error_log(ERROR_MAJOR, "AUTH instance not set in auth_fill_auth()");
	return -1;
    }

    if (authd->key == NULL) {
	authd->key = auth_compute_key(&authd->random, &authd->peer_random,
	    NULL);
    }

    computed_hmac_length = sizeof(computed_hmac);
    if (auth_compute_hmac_buf(authd->digest, authd->key,
	buf, computed_hmac, &computed_hmac_length) < 0) {
	return -1;
    }

    length = ntohs(auth_chunk->chunk_header.chunk_length);
    if (length < FIXED_AUTH_CHUNK_SIZE + computed_hmac_length) {
	error_logii(ERROR_MAJOR, "auth chunk too short: %d(needed) %d(actual)",
	    FIXED_AUTH_CHUNK_SIZE + computed_hmac_length, length);
	return -1;
    }
    memcpy(auth_chunk->hmac, computed_hmac, computed_hmac_length);

    return 0;
}


int auth_recv_auth_chunk(SCTP_auth_chunk *auth_chunk, size_t size)
{
    auth_data *authd = NULL;
    unsigned short length = 0;
    unsigned short shared_key_id = 0;
    unsigned short hmac_id = 0;
    size_t hmac_length = 0;
    unsigned char hmac[SCTP_AUTH_DIGEST_LEN_MAX];
    size_t computed_hmac_length = 0;
    unsigned char computed_hmac[SCTP_AUTH_DIGEST_LEN_MAX];

    event_logi(VVERBOSE, "auth_recv_auth_chunk: size(%d)",
	size);
    authd = (auth_data *)mdi_readSCTP_auth();
    if (!authd) {
	error_log(ERROR_MAJOR, "AUTH instance not set in auth_recv_auth()");
	return -1;
    }

    length = ntohs(auth_chunk->chunk_header.chunk_length);
    shared_key_id = ntohs(auth_chunk->shared_key_id);
    hmac_id = ntohs(auth_chunk->hmac_id);

    event_logiii(VVERBOSE, "auth_recv_auth_chunk: length(%d), shared_key_id(%d), hmac_id(%d)",
	length, shared_key_id, hmac_id);

    if (authd->key == NULL) {
	authd->key = auth_compute_key(&authd->random, &authd->peer_random,
	    NULL);
    }

    if (length < FIXED_AUTH_CHUNK_SIZE + authd->digest->length) {
	error_logii(ERROR_MAJOR, "chunk too short: %d(needed), %d(actual)",
	    FIXED_AUTH_CHUNK_SIZE + authd->digest->length, length);
	return -1;
    }

    memset(hmac, 0, sizeof(hmac));
    memset(computed_hmac, 0, sizeof(computed_hmac));

    hmac_length = length - FIXED_AUTH_CHUNK_SIZE;
    memcpy(hmac, auth_chunk->hmac, hmac_length);

    memset(auth_chunk->hmac, 0, hmac_length);

    computed_hmac_length = sizeof(computed_hmac);
    if (auth_compute_hmac(authd->digest, authd->key,
	(unsigned char *)auth_chunk, size,
	computed_hmac, &computed_hmac_length) < 0) {
	return -1;
    }

    if (computed_hmac_length != hmac_length ||
	memcmp(computed_hmac, hmac, hmac_length) != 0) {
	return -1;
    }

    return 0;
}


static int auth_compute_hmac(auth_digest *digest, auth_key *key,
    unsigned char *data, size_t data_length,
    unsigned char *computed_hmac, size_t *computed_hmac_length)
{
    auth_digest_ctx digest_ctx;

    unsigned char *key_data = NULL;
    size_t key_length = 0;
    unsigned char temp[SCTP_AUTH_DIGEST_LEN_MAX];
    size_t temp_length = 0;

    unsigned char ipad[SCTP_AUTH_DIGEST_BLOCK_LEN_MAX];
    unsigned char opad[SCTP_AUTH_DIGEST_BLOCK_LEN_MAX];
    unsigned int i;

    event_logiii(VVERBOSE, "auth_compute_hmac: key length(%d), data_length(%d), *computed_hmac_length(%d)",
	key->length, data_length, *computed_hmac_length);
    if (key->length > digest->block_length) {
	digest->initialize(&digest_ctx);
	digest->update(&digest_ctx, key->data, key->length);
	temp_length = sizeof(temp);
	digest->digest(&digest_ctx, temp, &temp_length);
	key_data = temp;
	key_length = temp_length;
    } else {
	key_data = key->data;
	key_length = key->length;
    }

    memset(ipad, 0, sizeof(ipad));
    memset(opad, 0, sizeof(opad));
    memcpy(ipad, key_data, key_length);
    memcpy(opad, key_data, key_length);
    
    for (i = 0; i < digest->block_length; i++) {
	ipad[i] ^= 0x36;
	opad[i] ^= 0x5c;
    }

    digest->initialize(&digest_ctx);
    digest->update(&digest_ctx, ipad, digest->block_length);
    digest->update(&digest_ctx, data, data_length);
    temp_length = sizeof(temp);
    digest->digest(&digest_ctx, temp, &temp_length);

    digest->initialize(&digest_ctx);
    digest->update(&digest_ctx, opad, digest->block_length);
    digest->update(&digest_ctx, temp, temp_length);
    temp_length = sizeof(temp);
    digest->digest(&digest_ctx, computed_hmac, computed_hmac_length);

    return 0;
}


static int
auth_compute_hmac_buf(auth_digest *digest, auth_key *key, adl_message_buf *buf,
    unsigned char *computed_hmac, size_t *computed_hmac_length)
{
    auth_digest_ctx digest_ctx;

    unsigned char *key_data = NULL;
    size_t key_length = 0;
    unsigned char temp[SCTP_AUTH_DIGEST_LEN_MAX];
    size_t temp_length = 0;

    unsigned char ipad[SCTP_AUTH_DIGEST_BLOCK_LEN_MAX];
    unsigned char opad[SCTP_AUTH_DIGEST_BLOCK_LEN_MAX];
    unsigned int i;

    if (key->length > digest->block_length) {
	digest->initialize(&digest_ctx);
	digest->update(&digest_ctx, key->data, key->length);
	temp_length = sizeof(temp);
	digest->digest(&digest_ctx, temp, &temp_length);
	key_data = temp;
	key_length = digest->length;
    } else {
	key_data = key->data;
	key_length = key->length;
    }

    memset(ipad, 0, sizeof(ipad));
    memset(opad, 0, sizeof(opad));
    memcpy(ipad, key_data, key_length);
    memcpy(opad, key_data, key_length);
    
    for (i = 0; i < digest->block_length; i++) {
	ipad[i] ^= 0x36;
	opad[i] ^= 0x5c;
    }

    digest->initialize(&digest_ctx);
    digest->update(&digest_ctx, ipad, digest->block_length);
    for (i = buf->offset; i < (buf->offset + buf->cnt); i++) {
	digest->update(&digest_ctx, (unsigned char *)buf->iovec[i].iov_base,
	    buf->iovec[i].iov_len);
    }
    temp_length = sizeof(temp);
    digest->digest(&digest_ctx, temp, &temp_length);

    digest->initialize(&digest_ctx);
    digest->update(&digest_ctx, opad, digest->block_length);
    digest->update(&digest_ctx, temp, digest->length);
    digest->digest(&digest_ctx, computed_hmac, computed_hmac_length);

    return 0;
}
